package com.xingchi.tornado.core.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.HyperLogLogOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * redis操作工具类
 *
 * @author xingchi
 * @date 2024/11/11 21:02
 */
@Slf4j
@Component
@SuppressWarnings("unchecked")
public class RedisUtils implements InitializingBean {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    private ValueOperations<String, Object> valueOperations;
    private HashOperations<String, String, Object> hashOperations;
    private SetOperations<String, Object> setOperations;
    private ListOperations<String, Object> listOperations;
    private ZSetOperations<String, Object> zSetOperations;
    private GeoOperations<String, Object> geoOperations;
    private HyperLogLogOperations<String, Object> hyperLogLogOperations;


    // =============================== string ============================
    public void set(String key, Object value) {
        valueOperations.set(key, value);
    }

    public <T> T get(String key) {
        return (T) valueOperations.get(key);
    }

    public void set(String key, Object value, Long time) {
        valueOperations.set(key, value, time, TimeUnit.SECONDS);
    }

    public void set(String key, Object value, Long time, TimeUnit unit) {
        valueOperations.set(key, value, time, unit);
    }

    public Long getExpire(String key) {
        return redisTemplate.getExpire(key);
    }

    public void expire(String key, Long second) {
        redisTemplate.expire(key, second, TimeUnit.SECONDS);
    }

    public void expire(String key, Long timeout, TimeUnit unit) {
        redisTemplate.expire(key, timeout, unit);
    }

    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    public Long incr(String key) {
        return valueOperations.increment(key);
    }

    public Long incr(String key, Long delta) {
        return valueOperations.increment(key, delta);
    }

    public Long decr(String key, Long delta) {
        return valueOperations.decrement(key, delta);
    }

    public Boolean setIfAbsent(String key, Object value) {
        return valueOperations.setIfAbsent(key, value);
    }

    public Boolean setIfAbsent(String key, Object value, Long time, TimeUnit unit) {
        return valueOperations.setIfAbsent(key, value, time, unit);
    }

    public Boolean setIfPresent(String key, Object value, Long time) {
        return valueOperations.setIfPresent(key, value, time, TimeUnit.SECONDS);
    }

    public Boolean setIfPresent(String key, Object value) {
        return valueOperations.setIfPresent(key, value);
    }

    public <T> T getAndSet(String key, Object value) {
        return (T) valueOperations.getAndSet(key, value);
    }

    public <T> T getAndDelete(String key) {
        return (T) valueOperations.getAndDelete(key);
    }

    public <T> T getAndExpire(String key, Long time, TimeUnit unit) {
        return (T) valueOperations.getAndExpire(key, time, unit);
    }

    // =============================== hash ============================
    public void hSet(String key, String hashKey, Object value) {
        hashOperations.put(key, hashKey, value);
    }

    public Boolean hSetIfAbsent(String key, String hashKey, Object value) {
        return hashOperations.putIfAbsent(key, hashKey, value);
    }

    public <T> T hGet(String key, String hashKey) {
        return (T) hashOperations.get(key, hashKey);
    }

    public Long hDelete(String key, Object... hashKeys) {
        return hashOperations.delete(key, hashKeys);
    }

    public Boolean hHasKey(String key, String hashKey) {
        return hashOperations.hasKey(key, hashKey);
    }

    public Long hIncrement(String key, String hashKey, Long delta) {
        return hashOperations.increment(key, hashKey, delta);
    }

    public Double hIncrement(String key, String hashKey, Double delta) {
        return hashOperations.increment(key, hashKey, delta);
    }

    public void hPutAll(String key, Map<String, Object> kv) {
        hashOperations.putAll(key, kv);
    }

    public Map<String, Object> hGetAll(String key) {
        return hashOperations.entries(key);
    }

    public Long hSize(String key) {
        return hashOperations.size(key);
    }

    // =============================== bit ============================
    public Boolean setBit(String key, Long offset, Boolean value) {
        return valueOperations.setBit(key, offset, value);
    }

    public Boolean getBit(String key, Long offset) {
        return valueOperations.getBit(key, offset);
    }

    public Long bitCount(String key) {
        return redisTemplate.execute((RedisCallback<Long>) connection ->
                connection.bitCount(key.getBytes(StandardCharsets.UTF_8))
        );
    }

    public void clear(String key) {
        redisTemplate.delete(key);
    }

    public Long or(String targetBitmap, String... bitSetNames) {
        return executeBitOp("OR", targetBitmap, bitSetNames);
    }


    public Long and(String targetBitmap, String... bitSetNames) {
        return executeBitOp("AND", targetBitmap, bitSetNames);
    }

    public Long xor(String targetBitmap, String... bitSetNames) {
        return executeBitOp("XOR", targetBitmap, bitSetNames);
    }

    public Long not(String targetBitmap, String... bitSetNames) {
        return executeBitOp("NOT", targetBitmap, bitSetNames);
    }

    private Long executeBitOp(String op, String targetBitmap, String... bitSetNames) {
        byte[][] bitsetNamesBytes = new byte[bitSetNames.length][];
        for (int i = 0; i < bitSetNames.length; i++) {
            bitsetNamesBytes[i] = bitSetNames[i].getBytes(StandardCharsets.UTF_8);
        }

        RedisStringCommands.BitOperation bitOperation = RedisStringCommands.BitOperation.valueOf(op);
        return redisTemplate.execute((RedisCallback<Long>) connection ->
                connection.bitOp(bitOperation, targetBitmap.getBytes(StandardCharsets.UTF_8), bitsetNamesBytes)
        );
    }

    @Override
    public void afterPropertiesSet() {
        valueOperations = redisTemplate.opsForValue();
        hashOperations = redisTemplate.opsForHash();
        setOperations = redisTemplate.opsForSet();
        listOperations = redisTemplate.opsForList();
        zSetOperations = redisTemplate.opsForZSet();
        geoOperations = redisTemplate.opsForGeo();
        hyperLogLogOperations = redisTemplate.opsForHyperLogLog();
        log.info("The redis operation tool class is initialized successfully!");
    }
}
